package com.example.spider.config.support;

import com.example.spider.task.Task;
import jakarta.annotation.Nullable;
import org.shoulder.core.log.Logger;
import org.shoulder.core.log.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;

import java.util.Set;

/**
 * @see AbstractHandlerExceptionResolver
 */
public abstract class AbstractSpiderExceptionResolver implements SpiderExceptionResolver, Ordered {

	/** Logger available to subclasses. */
	protected final Logger logger = LoggerFactory.getLogger(getClass());

	private int order = Ordered.LOWEST_PRECEDENCE;

	@Nullable
	private Set<?> mappedHandlers;

	@Nullable
	private Class<?>[] mappedHandlerClasses;

	@Nullable
	private Logger warnLogger;


	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}


	public void setMappedHandlers(@Nullable Set<?> mappedHandlers) {
		this.mappedHandlers = mappedHandlers;
	}


	public void setMappedHandlerClasses(Class<?>... mappedHandlerClasses) {
		this.mappedHandlerClasses = mappedHandlerClasses;
	}


	public void setWarnLogCategory(String loggerName) {
		this.warnLogger = (StringUtils.hasLength(loggerName) ? LoggerFactory.getLogger(loggerName) : null);
	}

	@Override
	@Nullable
	public <T> Object resolveException(Task<T> task, T spiderResult, @Nullable Object handler, Exception ex) {

		if (shouldApplyTo(task, handler)) {
			Object result = doResolveException(task, spiderResult, handler, ex);
			if (result != null) {
				// Print debug message when warn logger is not enabled.
				if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
					logger.debug(buildLogMessage(ex, task) + (result == null ? "" : " to " + result));
				}
				// Explicitly configured warn logger in logException method.
				logException(ex, task);
			}
			return result;
		}
		else {
			return null;
		}
	}

	protected boolean hasGlobalExceptionHandlers() {
		return false;
	}

	protected boolean shouldApplyTo(Task<?> task, @Nullable Object handler) {
		if (handler != null) {
			if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
				return true;
			}
			if (this.mappedHandlerClasses != null) {
				for (Class<?> handlerClass : this.mappedHandlerClasses) {
					if (handlerClass.isInstance(handler)) {
						return true;
					}
				}
			}
		}
		return !hasHandlerMappings();
	}


	protected boolean hasHandlerMappings() {
		return (this.mappedHandlers != null || this.mappedHandlerClasses != null);
	}


	protected void logException(Exception ex, Task<?> task) {
		if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
			this.warnLogger.warn(buildLogMessage(ex, task));
		}
	}


	protected String buildLogMessage(Exception ex, Task<?> task) {
		return "Resolved [" + LogFormatUtils.formatValue(ex, -1, true) + "]";
	}

	@Nullable
	protected abstract <T> Object doResolveException(Task<T> task, T spiderResult, @Nullable Object handler, Exception ex);

}